#include "minix.h"

static BOOLEAN minix_init(struct super_block * sb, dev_t dev);
static void minix_free(struct super_block * sb);
static BOOLEAN minix_iread(struct inode * inoptr, dev_t dev, ino_t ino);
static BOOLEAN minix_iwrite(struct inode * inoptr, dev_t dev, ino_t ino);
static BOOLEAN minix_ialloc(
	struct super_block * sb, struct inode * inoptr, mode_t mode
	);
static void minix_ifree(struct super_block * sb, ino_t ino);
static unsigned long minix_balloc(struct super_block * sb);
static void minix_bfree(struct super_block * sb, unsigned long block);
static int minix_statfs(struct super_block * sb, struct statfs * stat);

struct filesystem minix_filesystem = {
	&minix_init,
	&minix_free,
	&minix_iread,
	&minix_iwrite,
	&minix_ialloc,
	&minix_ifree,
	&minix_balloc,
	&minix_bfree,
	&minix_statfs,
};

static BOOLEAN minix_init(struct super_block * sb, dev_t dev)
{
	struct buf_head * bh;
	struct minix_super_block * msb;

	bh = bread(dev, SUPER_BLOCK);
	if(!bh){
		PRINTK("failed to read minix super block\n");
		return FALSE;
	}

	msb = (struct minix_super_block *)bh->b_data;

	// check magic first
	if(msb->s_magic != LM_MAGIC){
		brelse(bh);
		return FALSE;
	}

	// basic check
	if((msb->s_imblocks < 1)
		|| (msb->s_zmblocks < 1)
		|| (msb->s_ninodes < 1)
		|| (msb->s_zones < 1)
		|| (msb->s_lzsize != 0)){ // xxx
		brelse(bh);
		return FALSE;
	}

	msb = (struct minix_super_block *)
		kmalloc(sizeof(struct minix_super_block), PageWait);
	if(!msb){
		PRINTK("out of memory\n");
		brelse(bh);
		return FALSE;
	}

	memcpy(msb, bh->b_data, SUPER_SIZE);
	
	brelse(bh);

	msb->s_version = LM_MAGIC;
	msb->s_ipblock = INODES_PER_BLOCK;
	msb->s_ndzones = NR_DZONES;
	msb->s_nindirs = NR_INDIRECTS;

	msb->s_isearch = 0;
	msb->s_zsearch = 0;

	sb->s_filesystem = &minix_filesystem;
	sb->s_data = msb;
	
	return TRUE;
}

static void minix_free(struct super_block * sb)
{
	kfree(sb->s_data);
	sb->s_data = NULL;
}

#define icopy(dest, src) \
do{ \
	dest->i_mode = src->i_mode; \
	dest->i_nlinks = src->i_nlinks; \
	dest->i_uid = src->i_uid; \
	dest->i_gid = src->i_gid; \
	dest->i_size = src->i_size; \
	dest->i_atime = src->i_atime; \
	dest->i_mtime = src->i_mtime; \
	dest->i_ctime = src->i_ctime; \
	for(i = 0; i < NR_ZONES; i++) \
		dest->i_zone[i] = src->i_zone[i]; \
}while(0)

extern struct inode_operation minix_inode_operation;

/*
 * inoptr is not locked
 */
static BOOLEAN minix_iread(struct inode * inoptr, dev_t dev, ino_t ino)
{
	int i;
	unsigned long block;
	struct buf_head * bh;
	struct super_block * sb;
	struct minix_super_block * msb;
	struct minix_inode * minoptr, * tmp;

	if(ino == ROOT_INODE)
		ino = 1;

	sb = inoptr->i_sb;
	if(!sb)
		DIE("BUG: cannot happen\n");

	msb = (struct minix_super_block *)sb->s_data;

	// 2 for boot and super block
	block = 2 + msb->s_imblocks + msb->s_zmblocks;	

	// 0 inode number is saved, but inode, use it
	block += (ino - 1) / msb->s_ipblock;

	bh = bread(dev, block);
	if(!bh){
		PRINTK("failed to read inode\n");
		return FALSE;
	}

	minoptr = (struct minix_inode *)
		kmalloc(sizeof(struct minix_inode), PageWait);
	if(!minoptr){
		PRINTK("out of memory\n");
		brelse(bh);
		return FALSE;
	}

	// "- 1" for number-0 which is not-used
	tmp = (struct minix_inode *)
		(&bh->b_data[((ino - 1) % msb->s_ipblock) * INODE_SIZE]);

	icopy(minoptr, tmp);

	brelse(bh);

	minoptr->i_ndzones = NR_DZONES;
	minoptr->i_nindirs = NR_INDIRECTS;
	minoptr->i_msb = msb;

	inoptr->i_ops = &minix_inode_operation;
	inoptr->i_data = minoptr;

	return TRUE;
}

static BOOLEAN minix_iwrite(struct inode * inoptr, dev_t dev, ino_t ino)
{
	int i;
	unsigned long block;
	struct buf_head * bh;
	struct super_block * sb;
	struct minix_super_block * msb;
	struct minix_inode * minoptr, * tmp;

	if(ino == ROOT_INODE)
		ino = 1;

	sb = inoptr->i_sb;
	if(!sb)
		DIE("BUG: cannot happen\n");

	msb = (struct minix_super_block *)sb->s_data;

	block = 2 + msb->s_imblocks + msb->s_zmblocks;

	// number-0 is reserved
	block += (ino - 1) / msb->s_ipblock;
	bh = bread(inoptr->i_dev, block);
	if(!bh){
		PRINTK("failed to write inode\n");
		return FALSE;
	}

	minoptr = (struct minix_inode *)inoptr->i_data;

	tmp = (struct minix_inode *)
		(&bh->b_data[((ino - 1) % msb->s_ipblock) * INODE_SIZE]);

	icopy(tmp, minoptr);

#if 0
	bh->b_flags |= B_ASY;
	bwrite(bh);
#else
	bh->b_flags |= B_DIRTY | B_DONE;
	brelse(bh);
#endif

	return TRUE;
}

unsigned long minix_bitalloc(struct super_block * sb, int which)
{
	int i, j;
	unsigned long * tmp;
	unsigned long from_block, bit_blocks, block;
	unsigned long bit, max_bit, from_bit;
	struct buf_head * bh;
	struct minix_super_block * msb;

	if(sb->s_dev == NODEV)
		DIE("BUG: cannot happen\n");
	
	if(sb->s_roflag)
		return NOBIT;

	msb = (struct minix_super_block *)sb->s_data;
	if(!msb)
		DIE("BUG: cannot happen\n");

loop:
	// lock super block
	if(sb->s_flags & LOCK_BITOP){
		sb->s_flags |= WANT_BITOP;
		sleepon(&sb->s_bitop_wait);

		goto loop;
	}

	sb->s_flags |= LOCK_BITOP;

	if(which == IMAP){
		max_bit = msb->s_ninodes;
		from_block = SUPER_BLOCK + 1;
		from_bit = msb->s_isearch;
		bit_blocks = msb->s_imblocks;
	}else{
		max_bit = msb->s_zones - msb->s_fdzone;
		from_block = SUPER_BLOCK + 1 + msb->s_imblocks;
		from_bit = msb->s_zsearch;
		bit_blocks = msb->s_zmblocks;
	}

	// run round
	if(from_bit > max_bit)
		from_bit = 1; // reserve zero bit

	bit = NOBIT;

	block = (from_bit / BITS_PER_BLOCK);
	while(block < bit_blocks){
		bh = bread(sb->s_dev, from_block + block);
		if(!bh){
			printk("error when read bitmap\n");
			goto out;
		}

		tmp = (unsigned long *)bh->b_data;
		for(i = 0; i < (BLOCKSIZ / u32size); i++, tmp++)
			if(*tmp < 0xffffffff)
				break;
			
		if(i >= (BLOCKSIZ / u32size)){
			block++;
			brelse(bh);

			continue;
		}
		
		for(j = 0; (1 << j) & *tmp; j++);

		bit = block * BITS_PER_BLOCK + i * u32bits + j;

		if(bit > max_bit){
			brelse(bh);

			bit = NOBIT;
			goto out;
		}
	
		*tmp |= 1 << j;

#if 0
		bh->b_flags |= B_ASY;
		bwrite(bh);
#else
		bh->b_flags |= B_DIRTY | B_DONE;
		brelse(bh);
#endif

		if(which == IMAP)
			msb->s_isearch = bit + 1;
		else{
			msb->s_zsearch = bit + 1;
			bit += (msb->s_fdzone - 1); // xxx
		}

		break;
	}

out:
	// unlock super block
	sb->s_flags &= ~(LOCK_BITOP);
	if(sb->s_flags & WANT_BITOP){
		sb->s_flags &= ~WANT_BITOP;
		wakeup(&sb->s_bitop_wait);
	}

	return bit;
}

void minix_bitfree(struct super_block * sb, unsigned long bit, int which)
{
	unsigned long max_bit;
	unsigned long * tmp;
	unsigned long from_block, block;
	struct buf_head * bh;
	struct minix_super_block * msb;

	if(sb->s_dev == NODEV)
		DIE("BUG: cannot happen\n");
	
	if(sb->s_roflag)
		return;

	msb = (struct minix_super_block *)sb->s_data;
	if(!msb)
		DIE("BUG: cannot happen\n");

	if(which == IMAP){
		from_block = SUPER_BLOCK + 1;
		max_bit = msb->s_ninodes - 1;
	}else{
		from_block = SUPER_BLOCK + 1 + msb->s_imblocks;
		max_bit = msb->s_zones - msb->s_fdzone - 1;
		bit -= (msb->s_fdzone - 1);
	}

	if(bit > max_bit){
		printk("BUG want to free invalid bit!\n"); 

		return;
	}

loop:
	// lock super block
	if(sb->s_flags & LOCK_BITOP){
		sb->s_flags |= WANT_BITOP;
		sleepon(&sb->s_bitop_wait);

		goto loop;
	}

	sb->s_flags |= LOCK_BITOP;

	block = (bit / BITS_PER_BLOCK);	

	bh = bread(sb->s_dev, from_block + block);
	if(!bh){
		printk("read bitmap error\n");
		goto out;
	}	

	tmp = (unsigned long *)bh->b_data;

	bit %= BITS_PER_BLOCK;
	tmp += bit / u32bits;
	*tmp &= ~(1 << (bit % (u32bits)));
	
#if 0
		bh->b_flags |= B_ASY;
		bwrite(bh);
#else
		bh->b_flags |= B_DIRTY | B_DONE;
		brelse(bh);
#endif

	if(which == IMAP){
		if(bit < msb->s_isearch)
			msb->s_isearch = bit;
	}else{
		if(bit < msb->s_zsearch)
			msb->s_zsearch = bit;
	}

out:
	// unlock super block
	sb->s_flags &= ~(LOCK_BITOP);
	if(sb->s_flags & WANT_BITOP){
		sb->s_flags &= ~WANT_BITOP;
		wakeup(&sb->s_bitop_wait);
	}
}

static BOOLEAN minix_ialloc(
	struct super_block * sb, struct inode * inoptr, mode_t mode
	)
{
	int i;
	ino_t ino;
	struct minix_inode * minoptr;

	minoptr = (struct minix_inode *)
		kmalloc(sizeof(struct minix_inode), PageWait);
	if(!minoptr){
		printk("out of memory\n");
		return FALSE;
	}

	ino = (ino_t)minix_bitalloc(sb, IMAP);
	if((unsigned long)ino == NOBIT){
		kfree(minoptr);
		return FALSE;
	}

	minoptr->i_ndzones = NR_DZONES;
	minoptr->i_nindirs = NR_INDIRECTS;

	minoptr->i_mode = mode;
	minoptr->i_nlinks = 1;
	minoptr->i_uid = current->euid;
       	minoptr->i_gid = current->egid;
	minoptr->i_size = 0;
	for(i = 0; i < NR_ZONES; i++)
		minoptr->i_zone[i] = 0; // NOZONE

	minoptr->i_msb = (struct minix_super_block *)sb->s_data;

	inoptr->i_dev = sb->s_dev;
	inoptr->i_num = ino;

	inoptr->i_ops = &minix_inode_operation;
	inoptr->i_data = minoptr;	

	return TRUE;
}

static void minix_ifree(struct super_block * sb, ino_t ino)
{
	minix_bitfree(sb, ino, IMAP);
}

static unsigned long minix_balloc(struct super_block * sb)
{
	return minix_bitalloc(sb, ZMAP);
}

static void minix_bfree(struct super_block * sb, unsigned long block)
{
	minix_bitfree(sb, block, ZMAP);
}

static int get_zero_bits(unsigned long val, int bits)
{
	int count = 0;
	int i;

	if (bits > 32)
		bits = 32;

	for (i = 0; i < bits; i++)
	{
		if (!((val >> i) & 1))
		{
			count++;
		}
			
	}

	return count;
}

//
static int get_free_bits_nr(struct super_block *sb, int *pcount, int which)
{
	int i;
	int bit;
	int max_bit_index;
	int from_block;
	int max_block;
	int block;
	int count;
	int error = 0;
	unsigned long *tmp;
	struct buf_head *bh;
	struct minix_super_block *msb = (struct minix_super_block *)sb->s_data;

loop:
	// lock super block
	if(sb->s_flags & LOCK_BITOP)
	{
		sb->s_flags |= WANT_BITOP;
		sleepon(&sb->s_bitop_wait);

		goto loop;
	}

	sb->s_flags |= LOCK_BITOP;

	if (which == ZMAP)
	{
		// s_zones is the total blocks
		// s_fdzone is the index of first data zone
		max_bit_index = msb->s_zones - msb->s_fdzone;
		from_block = SUPER_BLOCK + 1 + msb->s_imblocks;
		max_block = msb->s_zmblocks;
	}
	else
	{
		max_bit_index = msb->s_ninodes;
		from_block = SUPER_BLOCK + 1;
		max_block = msb->s_imblocks;
	}

#if 0
	printk("wich = %s, from_block = %d, max_block = %d, max_bit = %d\n",
			which == ZMAP ? "zmap" : "imap",
			from_block,
			max_block,
			max_bit);
#endif
	count = 0;
	bit = 0;
	block = 0;
	while (block < max_block)
	{
		bh = bread(sb->s_dev, from_block + block);
		if (!bh)
		{
			printk("error when read bitmap\n");
			error = -EIO;
			goto out;
		}

		tmp = (unsigned long *)bh->b_data;
		for (i = 0; i < (BLOCKSIZ / u32size); i++, tmp++)
		{
			int bit_num;
			
			if (bit > max_bit_index)
			{
				break;
			}
			
			if (bit + 32 > max_bit_index)
			{
				bit_num = max_bit_index - bit + 1;
			}
			else
			{
				bit_num = 32;
			}
			
			count += get_zero_bits(*tmp, bit_num);
			bit += bit_num;
		}
		
		block++;
		brelse(bh);

		if (bit > max_bit_index)
		{
			break;
		}
	}

out:
	// unlock super block
	sb->s_flags &= ~(LOCK_BITOP);
	if(sb->s_flags & WANT_BITOP)
	{
		sb->s_flags &= ~WANT_BITOP;
		wakeup(&sb->s_bitop_wait);
	}

	*pcount = count;

#if 0
	printk("count = %d\n", count);
#endif	
	return error;
}

static int minix_statfs(struct super_block * sb, struct statfs * stat)
{
	int error, count;
	struct minix_super_block * msb;

	msb = (struct minix_super_block *)sb->s_data;

	stat->f_type = msb->s_magic;
	stat->f_bsize = BLOCKSIZ;
	stat->f_blocks = msb->s_zones - msb->s_fdzone;

	if ((error = get_free_bits_nr(sb, &count, ZMAP)) < 0)
	{
		return error;
	}

	stat->f_bfree = count;
	stat->f_bavail = count;
	stat->f_files = msb->s_ninodes;

	if ((error = get_free_bits_nr(sb, &count, IMAP)) < 0)
	{
		return error;
	}

	stat->f_ffree = count;
	stat->f_namelen = 30;

	return 0;
}
